package managementClient;

import analyticsServer.Event;
import analyticsServer.IAnalyticsServer;
import billingServer.Bill;
import billingServer.IBillingServerRMI;
import billingServer.IBillingServerSecure;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.rmi.AccessException;
import java.rmi.NoSuchObjectException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Properties;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Group 95
 */
public class Client extends UnicastRemoteObject implements IClientRMI {

    private static final String SUBSCRIPTION_CREATED="Created subscription with ID %s for events using filter '%s'";
    private static final Logger logger = Logger.getLogger("Client");
    private static final String INVALIDCOMMAND = "Error: invalid command!";
    private String serverName;
    private int serverPort;
    private IBillingServerRMI billingServer;
    private IBillingServerSecure secureServer;
    private IAnalyticsServer analyticsServer;
    private final UUID uniqueId = UUID.randomUUID();
    private EventBuffer eventBuffer;

    public Client(final String analyticsBind, final String billingBind) throws Exception{
        super();
        logger.setLevel(Level.OFF);
        try {
            loadServerProperties();
            Registry registry = LocateRegistry.getRegistry(serverName, serverPort);
            billingServer = (IBillingServerRMI) registry.lookup(billingBind);
            analyticsServer = (IAnalyticsServer) registry.lookup(analyticsBind);
            eventBuffer = new EventBuffer();
            logger.log(Level.INFO, "bind an AnalyticsServer: {0}", analyticsServer);
        } catch (NotBoundException ex) {
            logger.log(Level.SEVERE, null, ex);
            throw ex;
        } catch (AccessException ex) {
            logger.log(Level.SEVERE, null, ex);
            throw ex;
        }
    }

    /**
     * Connect to server. Wait for user-input to call login or further
     * server-command methods.
     */
    public void start() throws NoSuchAlgorithmException {
        InputStreamReader inputStream=null;
        BufferedReader brStdIn=null;
        try {
            inputStream = new InputStreamReader(System.in);
            brStdIn = new BufferedReader(inputStream);
            String strLine;
            while ((strLine = brStdIn.readLine()) != null &&billingServer!=null &&analyticsServer!=null) {
                if (strLine.startsWith("!exit")) {
                    stop();
                    break;
                } else if (strLine.startsWith("!")) {
                    logger.log(Level.INFO, "Call handleUserCommand for command: {0}", strLine);
                    handleUserCommand(strLine);
                } else {
                    System.out.println(INVALIDCOMMAND);
                }
            }
        } catch (IOException ex) {
            logger.log(Level.SEVERE, null, ex);
        }
        finally
        {
            if(inputStream!=null)
            {
                try {
                    inputStream.close();
                } catch (IOException ex) {
                    logger.log(Level.SEVERE, null, ex);
                }
            }
            if(brStdIn!=null)
            {
                try {
                    brStdIn.close();
                } catch (IOException ex) {
                    logger.log(Level.SEVERE, null, ex);
                }
            }
        }
    }

    /**
     * Stop manegementClient. Unexport remote objects, disconnect server.
     */
    private void stop() {
        logger.info("Exit");
        System.out.println("Exit management client.");
            try {
                UnicastRemoteObject.unexportObject(this, true);
                //UnicastRemoteObject.unexportObject(secureServer, true);
                //UnicastRemoteObject.unexportObject(analyticsServer, true);
            } catch (NoSuchObjectException ex) {
                logger.info("Unexport RMIs not necessary!");
            }
        secureServer = null;
        analyticsServer = null;


    }

    /**
     * Load registered users out of properties-file.
     *
     * @throws Exception
     */
    public final void loadServerProperties() throws Exception {
        InputStream is = ClassLoader.getSystemResourceAsStream("registry.properties");
        if (is != null) {
            Properties tmp = new java.util.Properties();
            try {
                tmp.load(is);
                serverName = tmp.getProperty("registry.host");
                serverPort = Integer.parseInt(tmp.getProperty("registry.port"));
            } catch (IOException iox) {
                //Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, iox);
                throw new Exception("Error while reading Properties file");
            } finally {
                try {
                    is.close();
                } catch (IOException ex) {
                    //ignore
                }
            }
        } else {
            // Logger.getLogger(Client.class.getName()).log(Level.SEVERE, "Properties file not found!");
            throw new Exception("Properties file not found!");
        }
    }

    /**
     * Login user. Login successfull if server-object not null.
     *
     * @param userCommand
     * @throws RemoteException
     * @throws NoSuchAlgorithmException
     */
    private void loginUser(String userCommand) throws RemoteException, NoSuchAlgorithmException {
        String[] parts = userCommand.split(" ");
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        md5.update(parts[2].getBytes());
        String password = new BigInteger(1, md5.digest()).toString(16);
        secureServer = billingServer.login(parts[1], password);
        if (secureServer != null) {
            System.out.println(parts[1] + " successfully logged in!");
        }

    }

    /**
     * Handle commands from userinput. Forward to BillingServer or
     * AnalyticsServer.
     *
     * @param userCommand
     */
    private void handleUserCommand(String userCommand) throws RemoteException, NoSuchAlgorithmException {
        boolean validCommand = false;

        if (userCommand.startsWith("!addStep")) {
            logger.info("HandleUserCommand addStep - forward to handleBillingServerCommand");
            validCommand = true;
            handleBillingServerCommand(userCommand);
        } else
        if (userCommand.startsWith("!steps")) {
            logger.info("HandleUserCommand steps");
            validCommand = true;
            handleBillingServerCommand(userCommand);
        } else
        if (userCommand.startsWith("!removeStep")) {
            logger.info("HandleUsercommand removeStep");
            validCommand = true;
            handleBillingServerCommand(userCommand);
        } else
        if (userCommand.startsWith("!logout")) {
            logger.info("HandleUserCommand logout");
            validCommand = true;
            handleBillingServerCommand(userCommand);
        } else
        if (userCommand.startsWith("!bill")) {
            logger.info("HandleUserCommand bill");
            validCommand = true;
            handleBillingServerCommand(userCommand);
        } else
        if (userCommand.startsWith("!subscribe")) {
            validCommand = true;
            logger.info("HandleUserCommand subscribe");
            handleAnalyticsServerCommand(userCommand);
        } else
        if (userCommand.startsWith("!unsubscribe")) {
            validCommand = true;
            logger.info("HandleUserCommand unsubscribe");
            handleAnalyticsServerCommand(userCommand);
        } else
        if (userCommand.startsWith("!login")) {
            validCommand = true;
            logger.info("HandleUserCommand login");
            handleBillingServerCommand(userCommand);
        } else
        if (userCommand.startsWith("!auto")) {
            validCommand = true;
            logger.info("HandleUserCommand auto");
            handleEventBufferCommand(userCommand);
        } else
        if (userCommand.startsWith("!hide")) {
            validCommand = true;
            logger.info("HandleUserCommand hide");
            handleEventBufferCommand(userCommand);
        } else
        if (userCommand.startsWith("!print")) {
            validCommand = true;
            logger.info("HandleUserCommand print");
            handleEventBufferCommand(userCommand);
        } 
        if (!validCommand) {
            System.out.println(INVALIDCOMMAND);
        }

    }

    /**
     * Forward user-commands to billingserver.
     *
     * @param userCommand
     */
    private void handleBillingServerCommand(String userCommand) throws RemoteException, NoSuchAlgorithmException {
        boolean loggedIn = false;
        if (secureServer != null) {
            loggedIn = true;
        }
        try {
            if (!loggedIn) {
                if (userCommand.startsWith("!login")) {
                    logger.log(Level.INFO, "Call loginUser for command: {0}", userCommand);
                    loginUser(userCommand);
                } else {
                    System.out.println("Need to login first");
                }
            }
            else
            if (loggedIn) {
                if (userCommand.startsWith("!addStep")) {
                    logger.info("AddStep.");
                    String[] parts = userCommand.split(" ");
                    if (parts.length >= 4) {
                        secureServer.createPriceStep(Double.parseDouble(parts[1]), Double.parseDouble(parts[2]), Double.parseDouble(parts[3]), Double.parseDouble(parts[4]));
                        if (Double.parseDouble(parts[2]) == 0) {
                            parts[2] = "INFINITY";
                        }
                        System.out.println("[" + parts[1] + " " + parts[2] + "] successfully added");
                    } else {
                        System.out.println(INVALIDCOMMAND + " Not enough values!");
                    }
                }
                if (userCommand.startsWith("!steps")) {
                    logger.info("Steps");
                    System.out.println(secureServer.getPriceSteps().toLine());
                } else
                if (userCommand.startsWith("!removeStep")) {
                    String[] parts = userCommand.split(" ");
                    if (parts.length >= 2) {
                        logger.log(Level.INFO, "RemoveStep: {0} {1}", new Object[]{parts[1], parts[2]});
                        try {
                            secureServer.deletePriceStep(Double.parseDouble(parts[1]), Double.parseDouble(parts[2]));
                            System.out.println("[" + parts[1] + " " + parts[2] + "] successfully removed");
                        } catch (RemoteException rex) {
                            System.out.println(rex.getMessage());
                        }
                    } else {
                        System.out.println(INVALIDCOMMAND + " Not enough values!");
                    }
                } else
                if (userCommand.startsWith("!logout")) {
                    logger.info("Logout");
                    secureServer = null;
                    System.out.println("Successfully logged out");
                } else
                if (userCommand.startsWith("!bill")) {
                    logger.info("Bill");
                    String[] parts = userCommand.split(" ");
                    if (parts.length < 2) {
                        System.out.println(INVALIDCOMMAND + " Not enough values!");
                    } else {
                        logger.log(Level.INFO, "Get bill for {0}", parts[1]);
                        Bill bill;
                        if ((bill = secureServer.getBill(parts[1])) != null) {
                            logger.log(Level.INFO, "Bill: {0}", bill);
                            System.out.println(bill.toLine());
                        } else {
                            System.out.println("No bill for " + parts[1] + " available");
                        }
                    }
                }
            }
        } catch (RemoteException rex) {
            System.out.println("Sorry, connection to BillingServer currently not available.");
        } catch (NoSuchAlgorithmException nsaex) {
            System.out.println("Error: cannot connect to BillingServer, invalid password!");
        } catch (IndexOutOfBoundsException iox) {
            System.out.println(INVALIDCOMMAND);
        } catch (NumberFormatException nfex) {
            System.out.println(INVALIDCOMMAND);
        }

    }

    /**
     * Forward user-commands to AnalyticsServer.
     *
     * @param userCommand
     */
    private void handleAnalyticsServerCommand(String userCommand) throws RemoteException {
        if (analyticsServer != null) {
            if (userCommand.startsWith("!subscribe")) {
                logger.info("Subscribe");
                String[] parts = userCommand.split("'");
                if (parts.length != 2) {
                    System.out.println(INVALIDCOMMAND);
                } else {
                    logger.log(Level.INFO, "Regex: {0}", parts[1]);
                    String id=analyticsServer.subscribe(parts[1], this);
                    if(id!=null)
                    {
                        System.out.println(String.format(SUBSCRIPTION_CREATED, id.substring(this.getId().length()),parts[1]));
                    }
                }
            } else
            if (userCommand.startsWith("!unsubscribe")) {
                logger.info("Unsubscribe");
                String[] parts = userCommand.split(" ");
                if (parts.length != 2) {
                    System.out.println(INVALIDCOMMAND);
                } else {
                    logger.log(Level.INFO, "SubscriptionID: {0}", parts[1]);
                    if(analyticsServer.unsubscribe(this.getId() + parts[1]))
                    {
                        System.out.println("subscription " + parts[1] + " terminated");
                    }
                    else
                    {
                        System.out.println("subscription " + parts[1] + " was not found");
                    }
                }
            }
        } else {
            System.out.println("Sorry, connection to AnalyticsServer currently not available.");
        }
    }
    
    /**
     * Handles userCommands that concearn the eventBuffer.
     * @param userCommand 
     */
    private void handleEventBufferCommand(String userCommand) {
        if (userCommand.startsWith("!auto")) {
            logger.info("EventCommand auto");
            eventBuffer.setAutoMode(true);
        } else
        if (userCommand.startsWith("!hide")) {
            logger.info("EventCommand hide");
            eventBuffer.setAutoMode(false);
        } else
        if (userCommand.startsWith("!print")) {
            logger.info("EventCommand print");
            System.out.println(eventBuffer.printEvents());
        }
    }



    /**
     * Processes incoming Events from AnalyticsServer.
     * Prints out Event if autoprint-Mode is activated
     * @param event
     * @throws RemoteException 
     */
    @Override
    public void processEvent(Event event) throws RemoteException {
        if(! eventBuffer.handleEvent(event)) {
            System.out.println(event.toString());
        }
    }
    
    /**
     * Subscribe to all Events, set EventBuffer to auto for loadTest.
     */
    public void setLoadTest() throws RemoteException {
        analyticsServer.subscribe(".*", this);
        eventBuffer.setAutoMode(true);
    }

    @Override
    public String getId() {
        return this.uniqueId.toString();
    }

    /*Client:
     * user/password = console
     * registry host&port = registry.properties
     * bindingnamen = build.xml (==> parameter)
     */
    public static void main(String args[]) {

        try {
            if (args.length == 0) {
                new Client("Analytics", "Billing").start();
            } else {
                new Client(args[0], args[1]).start();
            }
            

        } catch (Exception e) {
            if(e instanceof RemoteException || e instanceof NotBoundException)
            {
                System.out.println("Couldn't bind Billing or Analyticsserver");
            }
            else
            {
                System.out.println(e.toString());
            }
        }
    }
}